package com.jsbs.iam.ident.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jsbs.iam.common.core.dto.Result;
import com.jsbs.iam.common.core.utils.Json;
import com.jsbs.iam.common.core.utils.PageHelperTool;
import com.jsbs.iam.common.redis.service.RedisUtils;
import com.jsbs.iam.ident.constant.RedisConstants;
import com.jsbs.iam.ident.dto.BlacklistManagementPageDto;
import com.jsbs.iam.ident.dto.ExceptionLoginStrategyPageDto;
import com.jsbs.iam.ident.dto.IamIdentApplySystemDto;
import com.jsbs.iam.ident.entity.IamBlacklistManagement;
import com.jsbs.iam.ident.entity.IamExceptionLoginStrategy;
import com.jsbs.iam.ident.feign.IdentFeignInter;
import com.jsbs.iam.ident.mapper.IamBlacklistManagementMapper;
import com.jsbs.iam.ident.mapper.IamExceptionLoginStrategyMapper;
import com.jsbs.iam.ident.service.IamExceptionLoginStrategyService;
import com.jsbs.iam.ident.vo.IamBlacklistManagementVo;
import com.jsbs.iam.ident.vo.IamExceptionLoginStrategyVo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author zhouxiangyang
 * @className ExceptionLoginStrategyServiceImpl
 * @date 2022/12/23 11:46
 * @description:
 */
@Service
public class ExceptionLoginStrategyServiceImpl implements IamExceptionLoginStrategyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionLoginStrategyServiceImpl.class);

    @Autowired
    private IamExceptionLoginStrategyMapper iamExceptionLoginStrategyMapper;

    @Resource
    private RedissonClient redissonClient;

    @Autowired
    private IamBlacklistManagementMapper iamBlacklistManagementMapper;

    @Autowired
    private IdentFeignInter identFeignInter;

    @Value("${_env.name}")
    private String envName;

    /**
     * 异常登录策略分页查询
     * @return
     */
    @Override
    public PageInfo<IamExceptionLoginStrategyVo> getExceptionLoginStrategyPage (
            ExceptionLoginStrategyPageDto pageDto,String companyCode) {
        //初始化分页
        pageDto.init();
        PageHelper.startPage(pageDto.getCurrPage(),pageDto.getPageSize());
        //查询所有异常登录策略的数据
        List<IamExceptionLoginStrategy> exceptionLoginStrategyList =
                iamExceptionLoginStrategyMapper.getExceptionLoginStrategyAll(pageDto.getStrategyNameOrCode(),companyCode);
        PageInfo<IamExceptionLoginStrategy> page = new PageInfo<>(exceptionLoginStrategyList);
        exceptionLoginStrategyList.forEach(exceptionLoginStrategy ->{
            String applyTypeCode = exceptionLoginStrategy.getApplyTypeCode();
            String applyTypeName = getApplyTypeName(applyTypeCode);
            //展示的时候应展示应用名称
            exceptionLoginStrategy.setApplyTypeCode(applyTypeName);
        });

        //entity转vo
        List<IamExceptionLoginStrategyVo> iamExceptionLoginStrategyVos =
                convertExceptionLoginStrategyVo(exceptionLoginStrategyList);

        PageInfo<IamExceptionLoginStrategyVo> pageInfo = new PageInfo<>(iamExceptionLoginStrategyVos);
        //取总页数
        int total = (int) page.getTotal();
        PageInfo<IamExceptionLoginStrategyVo> exceptionPageInfo = PageHelperTool.initPageInfoObj(
                    pageDto.getCurrPage(), total, pageDto.getPageSize(), pageInfo);

        return exceptionPageInfo;
    }

    /**
     * 异常登录策略搜索
     * @param code
     * @return
     */
    @Override
    public List<IamExceptionLoginStrategy> getExceptionLoginStrategyList(String code) {
        //根据登录策略名称或者应用类型编码查询
        List<IamExceptionLoginStrategy> exceptionLoginStrategyList =
                iamExceptionLoginStrategyMapper.getExceptionLoginStrategyList(code);
        return exceptionLoginStrategyList;
    }

    /**
     * 新增异常登录策略
     * @param iamExceptionLoginStrategy
     * @return
     */
    @Override
    public int saveExceptionLoginStrategy(IamExceptionLoginStrategy iamExceptionLoginStrategy) throws Exception {

        if (iamExceptionLoginStrategy == null) {
            throw new Exception("新增异常登录策略时，入参不能为空");
        }
        if (StringUtils.isEmpty(iamExceptionLoginStrategy.getApplyTypeCode())) {
            throw new Exception("应用系统不能为空");
        }
        if (StringUtils.isEmpty(iamExceptionLoginStrategy.getExceptionLoginStrategyName())){
            throw new Exception("异常登陆策略名称不能为空");
        }

        // 新增异常登录策略时，策略编码加锁，失效时间10秒钟
        RLock lock = redissonClient.getLock(RedisConstants.EXCEPTION_LOGIN_CODE_LOCK);
        Long code = null;
        try {
            // 5分钟内重试获取锁，10秒后释放
            boolean isLocked = lock.tryLock(5 * 60 * 1000L, 10 * 1000L, TimeUnit.MILLISECONDS);
            // 获取到锁后，进行自增
            if (!isLocked) {
                LOGGER.warn("Route=>ExceptionLoginStrategyServiceImpl method=>saveExceptionLoginStrategy 新增异常登陆策略时策略编码生成失败:{}",
                        Json.toJsonString(iamExceptionLoginStrategy));
                throw new Exception();
            }
            // 加锁成功后获取当前缓存值
            String value = RedisUtils.get(envName + RedisConstants.EXCEPTION_LOGIN_CODE);
            // 若为空则从数据库取当前最大值，并加新增用户数量入缓存
            if (StringUtils.isEmpty(value)) {
                code = Long.valueOf(iamExceptionLoginStrategyMapper.getExceptionLoginStrategyCode()) + 1;
            } else {
                // 当前缓存获取成功，并加新增用户数量入缓存
                code = Long.valueOf(value) + 1;
            }
            // 将最新值更新到缓存中
            RedisUtils.set(envName + RedisConstants.EXCEPTION_LOGIN_CODE, String.valueOf(code));
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("Route=>ExceptionLoginStrategyServiceImpl method=>saveExceptionLoginStrategy 新增异常登陆策略时策略编码生成失败:{}",
                    Json.toJsonString(iamExceptionLoginStrategy));
            throw new RuntimeException("新增异常登录策略异常");
        } finally {
            // 手动解锁
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
        AtomicReference<Long> finalCode = new AtomicReference<>(code);

        String applyTypeName = iamExceptionLoginStrategy.getApplyTypeCode();
        //通过应用名称获取应用类型
        String applyTypeCode = getApplyTypeCode(applyTypeName);
        iamExceptionLoginStrategy.setApplyTypeCode(applyTypeCode);
        exceptionStrategyNameVerification(iamExceptionLoginStrategy);
        iamExceptionLoginStrategy.setExceptionLoginStrategyCode("CN" + String.format("%04d", finalCode.getAndSet(finalCode.get())));
        //新增异常登录策略
        int i = iamExceptionLoginStrategyMapper.insertSelective(iamExceptionLoginStrategy);
        return i;
    }

    /**
     * 编辑异常登录策略
     * @param iamExceptionLoginStrategy
     * @return
     */
    @Override
    public int updateExceptionLoginStrategy(IamExceptionLoginStrategy iamExceptionLoginStrategy)  throws Exception{
        String applyName = iamExceptionLoginStrategy.getApplyTypeCode();
        if (StringUtils.isNotEmpty(applyName)) {
            //根据应用系统名称获取应用系统编码
            String applyTypeCode = getApplyTypeCode(applyName);
            iamExceptionLoginStrategy.setApplyTypeCode(applyTypeCode);
        }
        //校验策略名称和应用编码是否重复
        exceptionStrategyNameVerification(iamExceptionLoginStrategy);
        iamExceptionLoginStrategy.setUpdateTime(new Date());
        int i = iamExceptionLoginStrategyMapper.updateByPrimaryKey(iamExceptionLoginStrategy);
        return i;
    }

    /**
     * 删除/批量删除异常登录策略
     * @param list
     * @return
     */
    @Override
    public int deleteExceptionLoginStrategy(List<String> list,String companyCode) {
        int i = iamExceptionLoginStrategyMapper.deleteByPrimaryCode(list,companyCode);
        return i;
    }

    /**
     * 黑名单分页查询。搜索
     * @param pageDto
     * @return
     */
    @Override
    public PageInfo<IamBlacklistManagementVo> getBlacklistManagementPage(BlacklistManagementPageDto pageDto) {
        //初始化分页
        pageDto.init();
        PageHelper.startPage(pageDto.getCurrPage(),pageDto.getPageSize());
        //查询所有黑名单的数据
        List<IamBlacklistManagement> blacklistManagementList =
                iamBlacklistManagementMapper.getBlacklistManagementAll(pageDto.getUserIdOrPhone());
        PageInfo<IamBlacklistManagement> page = new PageInfo<>(blacklistManagementList);
        //entity转vo
        List<IamBlacklistManagementVo> IamBlacklistManagementVos =
                convertBlacklistManagementVo(blacklistManagementList);

        PageInfo<IamBlacklistManagementVo> pageInfo = new PageInfo<>(IamBlacklistManagementVos);
        //取总页数
        int total = (int) page.getTotal();
        PageInfo<IamBlacklistManagementVo> blacklistPageInfo = PageHelperTool.initPageInfoObj(
                    pageDto.getCurrPage(), total, pageDto.getPageSize(), pageInfo);

        return blacklistPageInfo;
    }

    @Override
    public int saveBlacklistManagement(List<IamBlacklistManagement> iamBlacklistManagements){

        iamBlacklistManagements.forEach(blacklistManagement ->{
            String userId = blacklistManagement.getUserId();
            IamBlacklistManagement iamBlacklistManagement =
                    iamBlacklistManagementMapper.selectByPrimaryKey(userId);
            if (iamBlacklistManagement != null) {
                throw new RuntimeException("该账号已经存在黑名单，无需再次添加");
            }
        });
        int i = iamBlacklistManagementMapper.insertSelective(iamBlacklistManagements);
        return i;
    }

    @Override
    public int deleteBlacklistManagement(List<String> userId) {
        int i = iamBlacklistManagementMapper.deleteByPrimaryKey(userId);
        return i;
    }

    /**
     * 黑名单entity转vo
     * @param blacklistManagementList
     * @return
     */
    private List<IamBlacklistManagementVo> convertBlacklistManagementVo (List<IamBlacklistManagement> blacklistManagementList) {
        List<IamBlacklistManagementVo> list = new ArrayList<>();
        blacklistManagementList.forEach(IamBlacklistManagement -> {
            IamBlacklistManagementVo blacklistManagementVo = new IamBlacklistManagementVo();
            BeanUtils.copyProperties(IamBlacklistManagement,blacklistManagementVo);
            list.add(blacklistManagementVo);
        });
        return list;
    }

    /**
     * 异常登录策略entity转vo
     * @param exceptionLoginStrategyList
     * @return
     */
    private List<IamExceptionLoginStrategyVo> convertExceptionLoginStrategyVo
            (List<IamExceptionLoginStrategy> exceptionLoginStrategyList){
        List<IamExceptionLoginStrategyVo> list = new ArrayList<>();
        exceptionLoginStrategyList.forEach(exceptionLoginStrategy ->{
            IamExceptionLoginStrategyVo exceptionLoginStrategyVo = new IamExceptionLoginStrategyVo();
            BeanUtils.copyProperties(exceptionLoginStrategy,exceptionLoginStrategyVo);
            Date createTime = exceptionLoginStrategy.getCreateTime();
            //创建时间转换成String
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String strDate = sdf.format(createTime);
            exceptionLoginStrategyVo.setCreateTime(strDate);

            //异常登录开始时间转换
            Date startTime = exceptionLoginStrategy.getExceptionLoginStartTime();
            if (startTime != null) {
                exceptionLoginStrategyVo.setExceptionLoginStartTime(sdf.format(startTime));
            }
            //异常登录结束时间转换
            Date endTime = exceptionLoginStrategy.getExceptionLoginEndTime();
            if (endTime != null) {
                exceptionLoginStrategyVo.setExceptionLoginEndTime(sdf.format(endTime));
            }
            list.add(exceptionLoginStrategyVo);
        });
        return list;
    }

    private String getApplyTypeCode (String applyName) {
        IamIdentApplySystemDto iamApplySystemDto = new IamIdentApplySystemDto();
        iamApplySystemDto.setApplyName(applyName);
        //先查应用系统编码
        Result applyTypeCode = identFeignInter.getApplyTypeCode(iamApplySystemDto);
        return  applyTypeCode.getData().toString();
    }

    private String getApplyTypeName (String applyTypeCode)  {
        IamIdentApplySystemDto identApplySystemDto = new IamIdentApplySystemDto();
        identApplySystemDto.setApplyName(applyTypeCode);
        Result applyTypeName = identFeignInter.getApplyTypeName(identApplySystemDto);
        Object data = applyTypeName.getData();
        if (data != null) {
            return  data.toString();
        } else {
            LOGGER.info("应用类型没有对应的名称数据，使用应用类型返回{}",applyTypeCode);
            return applyTypeCode;
        }
    }

    //异常登录策略重名校验
    private void exceptionStrategyNameVerification (IamExceptionLoginStrategy iamExceptionLoginStrategy) throws Exception {
        String exceptionLoginStrategyName = iamExceptionLoginStrategy.getExceptionLoginStrategyName();
       if (StringUtils.isNotEmpty(exceptionLoginStrategyName)) {
           IamExceptionLoginStrategy exceptionStrategy =
                   iamExceptionLoginStrategyMapper.getExceptionStrategyName(exceptionLoginStrategyName);
            if (exceptionStrategy != null) {
                String codeDatabase = exceptionStrategy.getExceptionLoginStrategyCode();
                String code = iamExceptionLoginStrategy.getExceptionLoginStrategyCode();
                //数据库查询编码和入参编码如果一致，说明名称重复
                if (!codeDatabase.equals(code)) {
                    throw new Exception("异常登录策略名称重复，请更重新命名异常登录策略名称");
                }
            }
       }

        //应用系统编码
        String applyTypeCode = iamExceptionLoginStrategy.getApplyTypeCode();
        if (StringUtils.isNotEmpty(applyTypeCode)) {
            //根据应用系统编码查询策略
            IamExceptionLoginStrategy exceptionLoginStrategy = iamExceptionLoginStrategyMapper.getApplyTypeCode(applyTypeCode);
            if (exceptionLoginStrategy != null ){
                String codeDatabase = exceptionLoginStrategy.getExceptionLoginStrategyCode();
                String code = iamExceptionLoginStrategy.getExceptionLoginStrategyCode();
                //如果入参的策略编码和查询到的策略编码不一致则策略名称重复，进行异常抛出
                if (!codeDatabase.equals(code)) {
                    throw new Exception("该应用已经绑定了异常登录策略");
                }
            }
        }

    }
}